using System;
using C5;
using System.Text;

namespace CoreCalc.SheetDefinedFunctions
{
  public class StrictCellTypeHolder
  {
    /// <summary>
    /// The strict variables that are not part of the result.
    /// sestoft: strictUnchangeable contains ... what?
    /// The idea may be that valueChangeable is the set of those
    /// variables that may directly produce the result of the expression;
    /// and strictUnchangeable the set of those variables need to compute
    /// the expression, but which do not directly produce its value?
    /// </summary>
    private HashSet<VariableAndType> strictUnchangeable;
    private HashSet<FullCellAddr> valueChangeAble;

    /// <summary>
    /// The strict variables that are not part of the result
    /// </summary>
    public System.Collections.Generic.IEnumerable<VariableAndType> StrictUnchangeable
    {
      get
      {
        if (strictUnchangeable == null)
          return new VariableAndType[0];
        return strictUnchangeable;
      }
    }

    /// <summary>
    /// The strict variables that are part of the end result
    /// </summary>
    public System.Collections.Generic.IEnumerable<FullCellAddr> ValueChangeAble
    {
      get
      {
        if (valueChangeAble == null)
          return new FullCellAddr[0];
        return valueChangeAble;
      }
    }

    public StrictCellTypeHolder(bool createSets)
    {
      if (createSets)
      {
        strictUnchangeable = new HashSet<VariableAndType>();
        valueChangeAble = new HashSet<FullCellAddr>();
      }
    }

    public StrictCellTypeHolder(FullCellAddr var)
    {
      valueChangeAble = new HashSet<FullCellAddr>();
      valueChangeAble.Add(var);
    }

    public void Union(StrictCellTypeHolder other)
    {
      // sestoft: The Typ.Value argument is bogus (was Typ.NotApplicable) but 
      // does not affect the correctness or efficiency of the code generated.
      // The only thing that matters in ProgramLines.??? for a variable x 
      // is whether (x,Number) appears in valueChangeable.
      Union(other, Typ.Value); 
    }

    // The point of newType is to set the "expected" type of all those strictly
    // used variables that could produce the result of this expression.
    // Called from CGStrictOperation.GetStrictCells to note that the "result"
    // variables in this expression are expected to have type expectedType.
    public void Union(StrictCellTypeHolder other, Typ expectedType)
    {
      UnionResults(other, expectedType);
      UnionNonResults(other);
    }

    private void UnionNonResults(StrictCellTypeHolder other)
    {
      InitStrictUnchangeable();

      foreach (VariableAndType holder in other.StrictUnchangeable)
      {
        strictUnchangeable.Add(holder);
      }
    }

    private void UnionResults(StrictCellTypeHolder other, Typ newType)
    {
      // The newType argument is used 
      if (this.valueChangeAble == null && other.valueChangeAble == null)
        return;

      if (valueChangeAble == null)
      {
        valueChangeAble = new HashSet<FullCellAddr>();
      }

      InitStrictUnchangeable();

      // sestoft: The intention of this is to note that a variable (or expression) in argument
      // position must have the type that the function expects that argument to 
      // have.  However, the implementation is incomplete, since it ignores all positions
      // of IF, AND and OR.  However, it is sensible in the sense that it records all
      // expected types of a variable, such as { (x,Number), (x,Value) } instead of only one.
      // This information is used in ...?
      foreach (FullCellAddr variable in ValueChangeAble)
      {
        strictUnchangeable.Add(new VariableAndType(variable, newType));
      }
      valueChangeAble = null;

      foreach (FullCellAddr variable in other.ValueChangeAble)
      {
        strictUnchangeable.Add(new VariableAndType(variable, newType));
      }
    }


    private void InitStrictUnchangeable()
    {
      if (strictUnchangeable == null)
        strictUnchangeable = new HashSet<VariableAndType>();
    }

    public void Intersection(StrictCellTypeHolder other)
    {
      IntersectionResults(other);
      IntersectionNonResults(other);
    }

    public void IntersectionResults(StrictCellTypeHolder other)
    {
      if (other.valueChangeAble == null || this.valueChangeAble == null)
      {
        valueChangeAble = null;
        return;
      }

      HashSet<FullCellAddr> newValueChangeAble = new HashSet<FullCellAddr>();
      foreach (FullCellAddr var in other.ValueChangeAble)
      {
        if (valueChangeAble.Contains(var))
          newValueChangeAble.Add(var);
      }
      valueChangeAble = newValueChangeAble;
    }

    public void IntersectionNonResults(StrictCellTypeHolder other)
    {
      if (other.strictUnchangeable == null || this.strictUnchangeable == null)
      {
        this.strictUnchangeable = null;
        return;
      }
      //TODO: if two types are to be capable of having a more specific common type 
      //TODO: than value this should be implemented
      HashSet<VariableAndType> newUnChangeable = new HashSet<VariableAndType>();
      foreach (VariableAndType value in other.StrictUnchangeable)
      {
        if (strictUnchangeable.Contains(value))
        {
          newUnChangeable.Add(value);
        }
      }
      strictUnchangeable = newUnChangeable;

    }

    public void InsertAllAsNonResults(StrictCellTypeHolder other)
    {
      UnionNonResults(other);
      foreach (FullCellAddr variable in ValueChangeAble)
      {
        strictUnchangeable.Add(new VariableAndType(variable, Typ.Value));
      }
      foreach (FullCellAddr variable in other.ValueChangeAble)
      {
        strictUnchangeable.Add(new VariableAndType(variable, Typ.Value));
      }
    }

    public VariableAndType[] AllToArray()
    {
      HashSet<VariableAndType> back = new HashSet<VariableAndType>();
      back.AddAll(StrictUnchangeable);
      foreach (FullCellAddr var in ValueChangeAble)
      {
        back.Add(new VariableAndType(var, Typ.Value)); 
        // Typ.Value above means don't bother with early unwrapping
      }
      return back.ToArray();
    }
  }

  public struct VariableAndType : IEquatable<VariableAndType>
  {
    public readonly FullCellAddr var;
    public readonly Typ type;

    public VariableAndType(FullCellAddr var, Typ type)
    {
      this.var = var;
      this.type = type;
    }

    public bool Equals(VariableAndType variableAndType)
    {
      return Equals(var, variableAndType.var) && Equals(type, variableAndType.type);
    }

    public override bool Equals(object obj)
    {
      return obj is VariableAndType && Equals((VariableAndType)obj);
    }

    public override int GetHashCode()
    {
      return var.GetHashCode() + 29 * type.GetHashCode();
    }
  }
}